// ============================================================================
// FileName: SIPSwitchXMLPersistor.cs
//
// Description:
// Retrieves and persists sipswitch objects from XML files.
//
// Author(s):
// Aaron Clauson
//
// History:
// 14 Sep 2008	Aaron Clauson	Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2008 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
// ============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using BlueFace.Sys;
using BlueFace.VoIP.Net.SIP;
using BlueFace.VoIP.SIPServer;
using BlueFace.VoIP.SIPServerCores.StatefulProxy;
using log4net;

namespace BlueFace.VoIP.SIP.StatefulProxy
{
    public class SIPSwitchXMLPersistor : SIPSwitchPersistor
    {
        public const string XML_DOMAINS_FILENAME = "domains.xml";
        public const string XML_SIPACCOUNTS_FILENAME = "sipaccounts.xml";
        public const string XML_SIPPROVIDERS_FILENAME = "sipproviders.xml";
        public const string XML_DIALPLANS_FILENAME = "dialplans.xml";
        public const string XML_EXTENSIONS_FILENAME = "extensions.xml";
        private const int RELOAD_SPACING_SECONDS = 3;                           // Minimum interval the XML file change events will be allowed.

        private string m_xmlFileDirectory;
        private string m_xmlSIPAccountsFilePath;
        private string m_xmlDomainsFilePath;
        private string m_xmlSIPProvidersFilePath;
        private string m_xmlDialplansFilePath;
        private string m_xmlExtensionsFilePath;

        private ILog logger = AssemblyStateProxy.logger;

        private XmlDocument m_domainsDOM;
        private List<SIPAccount> m_sipAccounts;
        private List<SIPProvider> m_sipProviders;
        private XmlDocument m_dialPlansDOM;
        private XmlDocument m_extensionsDOM;

        private FileSystemWatcher m_domainsWatcher;
        private DateTime m_domainsLastReload;
        private FileSystemWatcher m_sipAccountsWatcher;
        private DateTime m_sipAccountsLastReload;
        private FileSystemWatcher m_sipProvidersWatcher;
        private DateTime m_sipProvidersLastReload;
        private FileSystemWatcher m_dialplansWatcher;
        private DateTime m_dialplansLastReload;
        private FileSystemWatcher m_extensionsWatcher;
        private DateTime m_extensionsLastReload;

        public override event SIPDomainsUpdatedDelegate DomainsUpdated;
        public override event SIPProvidersUpdatedDelegate SIProvidersUpdated;
        public override event SIPDialPlansUpdatedDelegate DialplansUpdated;

        public SIPSwitchXMLPersistor(StorageTypes storageType, string storageConnectionStr)
            : base(storageType, storageConnectionStr)
        {
            m_xmlFileDirectory = storageConnectionStr;

            if (!Directory.Exists(m_xmlFileDirectory))
            {
                logger.Warn("Directory " + m_xmlFileDirectory + " does not exist for XML persistor.");
            }
            else
            {
                m_xmlDomainsFilePath = m_xmlFileDirectory + XML_DOMAINS_FILENAME;
                m_xmlSIPAccountsFilePath = m_xmlFileDirectory + XML_SIPACCOUNTS_FILENAME;
                m_xmlSIPProvidersFilePath = m_xmlFileDirectory + XML_SIPPROVIDERS_FILENAME;
                m_xmlDialplansFilePath = m_xmlFileDirectory + XML_DIALPLANS_FILENAME;
                m_xmlExtensionsFilePath = m_xmlFileDirectory + XML_EXTENSIONS_FILENAME;

                LoadDomainsXML();
                LoadSIPAccountsXML();
                LoadProvidersXML();
                LoadDialplansXML();
                LoadExtensionsXML();
            }  
        }

        private void LoadDomainsXML()
        {
            if (!File.Exists(m_xmlDomainsFilePath))
            {
                logger.Warn("XML persistor cannot load SIP domains XML, file does not exist, " + m_xmlDomainsFilePath + ".");
            }
            else
            {
                string copyFilename = m_xmlDomainsFilePath + ".copy";
                File.Copy(m_xmlDomainsFilePath, copyFilename, true);

                m_domainsDOM = new XmlDocument();
                m_domainsDOM.Load(copyFilename);

                if (m_domainsDOM == null || m_domainsDOM.DocumentElement == null)
                {
                    logger.Warn("XML persistor cannot load SIP domains XML, invalid XML, " + m_xmlDomainsFilePath + ".");
                }

                if (m_domainsWatcher == null)
                {
                    logger.Debug("Starting file watch on " + m_xmlFileDirectory + " and " + XML_DOMAINS_FILENAME + ".");
                    m_domainsWatcher = new FileSystemWatcher(m_xmlFileDirectory, XML_DOMAINS_FILENAME);
                    m_domainsWatcher.Changed += new FileSystemEventHandler(DomainsChanged);
                    m_domainsWatcher.EnableRaisingEvents = true;
                }
            }
        }

        private void DomainsChanged(object sender, FileSystemEventArgs e)
        {
            if (DateTime.Now.Subtract(m_domainsLastReload).TotalSeconds > RELOAD_SPACING_SECONDS)
            {
                try
                {
                    m_domainsLastReload = DateTime.Now;
                    LoadDomainsXML();

                    if (DomainsUpdated != null)
                    {
                        DomainsUpdated(LoadSIPDomains());
                    }
                }
                catch (Exception excp)
                {
                    logger.Error("Exception DomainsChanged. " + excp.Message);
                }
            }
        }

        private void LoadSIPAccountsXML()
        {
            if (!File.Exists(m_xmlSIPAccountsFilePath))
            {
                logger.Warn("XML persistor cannot load SIP accounts XML, file does not exist, " + m_xmlSIPAccountsFilePath + ".");
            }
            else
            {
                string copyFilename = m_xmlSIPAccountsFilePath + ".copy";
                File.Copy(m_xmlSIPAccountsFilePath, copyFilename, true);

                DataSet sipAccountsSet = new DataSet();
                sipAccountsSet.ReadXml(copyFilename);

                if (sipAccountsSet == null)
                {
                    logger.Warn("XML persistor cannot load SIP accounts XML, invalid XML, " + m_xmlSIPAccountsFilePath + ".");
                }
                else
                {
                    m_sipAccounts = new List<SIPAccount>();

                    foreach (DataRow row in sipAccountsSet.Tables[0].Rows)
                    {
                        try
                        {
                            SIPAccount sipAccount = new SIPAccount(row);
                            logger.Debug(" loaded SIP account for " + sipAccount.SIPUsername + "@" + sipAccount.SIPDomain + ".");
                            m_sipAccounts.Add(sipAccount);
                        }
                        catch (Exception excp)
                        {
                            logger.Error("Exception loading SIP account record in SIPSwitchXMLPersistor. " + excp.Message);
                        }
                    }
                }

                if (m_sipAccountsWatcher == null)
                {
                    logger.Debug("Starting file watch on " + m_xmlFileDirectory + " and " + XML_SIPACCOUNTS_FILENAME + ".");
                    m_sipAccountsWatcher = new FileSystemWatcher(m_xmlFileDirectory, XML_SIPACCOUNTS_FILENAME);
                    m_sipAccountsWatcher.Changed += new FileSystemEventHandler(SIPAccountsChanged);
                    m_sipAccountsWatcher.EnableRaisingEvents = true;
                }
            }
        }

        private void SIPAccountsChanged(object sender, FileSystemEventArgs e)
        {
            if (DateTime.Now.Subtract(m_sipAccountsLastReload).TotalSeconds > RELOAD_SPACING_SECONDS)
            {
                try
                {
                    m_sipAccountsLastReload = DateTime.Now;
                    LoadSIPAccountsXML();
                }
                catch (Exception excp)
                {
                    logger.Error("Exception SIPAccountsChanged. " + excp.Message);
                }
            }
        }

        private void LoadProvidersXML()
        {
            if (!File.Exists(m_xmlSIPProvidersFilePath))
            {
                logger.Warn("XML persistor cannot load SIP providers XML, file does not exist, " + m_xmlSIPProvidersFilePath + ".");
            }
            else
            {
                string copyFilename = m_xmlSIPProvidersFilePath + ".copy";
                File.Copy(m_xmlSIPProvidersFilePath, copyFilename, true);

                DataSet sipProvidersSet = new DataSet();
                sipProvidersSet.ReadXml(copyFilename);

                if (sipProvidersSet == null)
                {
                    logger.Warn("XML persistor cannot load SIP providers XML, invalid XML, " + m_xmlSIPProvidersFilePath + ".");
                }
                else
                {
                    m_sipProviders = new List<SIPProvider>();

                    foreach (DataRow row in sipProvidersSet.Tables[0].Rows)
                    {
                        try
                        {
                            SIPProvider sipProvider = new SIPProvider(row);
                            logger.Debug(" loaded SIP provider for " + sipProvider.Owner + " & " + sipProvider.ProviderName + ".");
                            m_sipProviders.Add(sipProvider);
                        }
                        catch (Exception excp)
                        {
                            logger.Error("Exception loading SIP provider record in SIPSwitchXMLPersistor. " + excp.Message);
                        }
                    }
                }

                if (m_sipProvidersWatcher == null)
                {
                    logger.Debug("Starting file watch on " + m_xmlFileDirectory + " and " + XML_SIPPROVIDERS_FILENAME + ".");
                    m_sipProvidersWatcher = new FileSystemWatcher(m_xmlFileDirectory, XML_SIPPROVIDERS_FILENAME);
                    m_sipProvidersWatcher.Changed += new FileSystemEventHandler(SIPProvidersChanged);
                    m_sipProvidersWatcher.EnableRaisingEvents = true;
                }
            }
        }

        private void SIPProvidersChanged(object sender, FileSystemEventArgs e)
        {
            if (DateTime.Now.Subtract(m_sipProvidersLastReload).TotalSeconds > RELOAD_SPACING_SECONDS)
            {
                try
                {
                    m_sipProvidersLastReload = DateTime.Now;
                    LoadProvidersXML();

                    if (SIProvidersUpdated != null)
                    {
                        SIProvidersUpdated();
                    }
                }
                catch (Exception excp)
                {
                    logger.Error("Exception SIPProvidersChanged. " + excp.Message);
                }
            }
        }

        private void LoadDialplansXML()
        {
            if (!File.Exists(m_xmlDialplansFilePath))
            {
                logger.Warn("XML persistor cannot load the dialplan XML, file does not exist, " + m_xmlDialplansFilePath + ".");
            }
            else
            {
                string copyFilename = m_xmlDialplansFilePath + ".copy";
                File.Copy(m_xmlDialplansFilePath, copyFilename, true);

                m_dialPlansDOM = new XmlDocument();
                m_dialPlansDOM.Load(copyFilename);

                if (m_dialPlansDOM == null || m_dialPlansDOM.DocumentElement == null)
                {
                    logger.Warn("XML persistor cannot load dialplan XML, invalid XML, " + m_xmlDialplansFilePath + ".");
                }

                if (m_dialplansWatcher == null)
                {
                    logger.Debug("Starting file watch on " + m_xmlFileDirectory + " and " + XML_DIALPLANS_FILENAME + ".");
                    m_dialplansWatcher = new FileSystemWatcher(m_xmlFileDirectory, XML_DIALPLANS_FILENAME);
                    m_dialplansWatcher.Changed += new FileSystemEventHandler(DialplansChanged);
                    m_dialplansWatcher.EnableRaisingEvents = true;
                }
            }
        }

        private void DialplansChanged(object sender, FileSystemEventArgs e)
        {
            if (DateTime.Now.Subtract(m_dialplansLastReload).TotalSeconds > RELOAD_SPACING_SECONDS)
            {
                try
                {
                    m_dialplansLastReload = DateTime.Now;
                    logger.Debug("Reloading dialplan DOM.");
                    LoadDialplansXML();

                    if (DialplansUpdated != null)
                    {
                        DialplansUpdated();
                    }
                }
                catch (Exception excp)
                {
                    logger.Error("Exception DialplansChanged. " + excp.Message);
                }
            }
        }

        private void LoadExtensionsXML()
        {
            if (!File.Exists(m_xmlExtensionsFilePath))
            {
                logger.Warn("XML persistor cannot load extensions XML, file does not exist, " + m_xmlExtensionsFilePath + ".");
            }
            else
            {
                string copyFilename = m_xmlExtensionsFilePath + ".copy";
                File.Copy(m_xmlExtensionsFilePath, copyFilename, true);

                m_extensionsDOM = new XmlDocument();
                m_extensionsDOM.Load(copyFilename);

                if (m_extensionsDOM == null || m_extensionsDOM.DocumentElement == null)
                {
                    logger.Warn("XML persistor cannot load extensions XML, invalid XML, " + m_xmlExtensionsFilePath + ".");
                }

                if (m_extensionsWatcher == null)
                {
                    logger.Debug("Starting file watch on " + m_xmlFileDirectory + " and " + XML_EXTENSIONS_FILENAME + ".");
                    m_extensionsWatcher = new FileSystemWatcher(m_xmlFileDirectory, XML_EXTENSIONS_FILENAME);
                    m_extensionsWatcher.Changed += new FileSystemEventHandler(ExtensionsChanged);
                    m_extensionsWatcher.EnableRaisingEvents = true;
                }
            }
        }

        private void ExtensionsChanged(object sender, FileSystemEventArgs e)
        {
            if (DateTime.Now.Subtract(m_extensionsLastReload).TotalSeconds > RELOAD_SPACING_SECONDS)
            {
                try
                {
                    m_extensionsLastReload = DateTime.Now;
                    LoadExtensionsXML();

                    logger.Debug("Extensions XML reloaded.");
                }
                catch (Exception excp)
                {
                    logger.Error("Exception ExtensionsChanged. " + excp.Message);
                }
            }
        }

        public override bool DoesSIPAccountExist(string user, string canonicalDomain)
        {
            foreach (SIPAccount sipAccount in m_sipAccounts)
            {
                if (sipAccount.SIPUsername == user && sipAccount.SIPDomain == canonicalDomain)
                {
                    return true;
                }
            }

            return false;
        }

        public override List<SIPAddressBinding> GetSIPAccountBindings(string user, string canonicalDomain)
        {
            throw new NotImplementedException("SIPSwitchXMLPersistor GetSIPAccountBindings.");
        }

        public override bool ProxyMonitorAuthenticate(string username, string password)
        {
            foreach (SIPAccount sipAccount in m_sipAccounts)
            {
                if (sipAccount.SIPUsername == username)
                {
                    if (password == sipAccount.SIPPassword)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            return false;
        }

        public override bool HasDialPlanBeenUpdated(string username, string domain)
        {
            return false;
        }

        public override string LoadDialPlanSource(string username)
        {
            if (m_dialPlansDOM == null || m_dialPlansDOM.DocumentElement == null)
            {
                return null;
            }
            else
            {
                XmlNode dialplanNode = m_dialPlansDOM.DocumentElement.SelectSingleNode("dialplan[@username='" + username + "']");

                if (dialplanNode != null)
                {
                    return dialplanNode.InnerText.Trim();
                }
            }

            return null;
        }

        public override string GetUserEmailAddress(string username)
        {
            return null;
        }

        public override List<SIPProvider> GetSIPProvidersForUser(string username)
        {
            List<SIPProvider> sipProviders = new List<SIPProvider>();

            foreach (SIPProvider sipProvider in m_sipProviders)
            {
                if (sipProvider.Owner == username)
                {
                    sipProviders.Add(sipProvider);
                }
            }

            return sipProviders;
        }

        public override string GetExtensionOwner(string user, string domain)
        {
            if (m_extensionsDOM != null && m_extensionsDOM.DocumentElement != null)
            {
                XmlNode extensionNode = m_extensionsDOM.DocumentElement.SelectSingleNode("extension[@domain='" + domain + "' and @extension='" + user + "']");
                if (extensionNode != null)
                {
                    return extensionNode.InnerText;
                }
            }

            return null;
        }

        public override string GetSIPAccountOwner(string user, string domain)
        {
            SIPAccount sipAccount = LoadSIPAccount(user, domain);

            if (sipAccount != null)
            {
                return sipAccount.Owner;
            }
            else
            {
                return null;
            }
        }

        public override SIPAccount LoadSIPAccount(string username, string domain)
        {
            if(m_sipAccounts == null)
            {
                return null;
            }
            else
            {
                foreach (SIPAccount sipAccount in m_sipAccounts)
                {
                    if (sipAccount.SIPUsername == username && sipAccount.SIPDomain == domain)
                    {
                        return sipAccount;
                    }
                }

                return null;
            }
        }

        public override SIPDomains LoadSIPDomains()
        {
            if (m_domainsDOM != null)
            {
                SIPDomains domains = new SIPDomains();

                foreach (XmlNode domainNode in m_domainsDOM.DocumentElement.SelectNodes("domain"))
                {
                    string domain = domainNode.SelectSingleNode("value").InnerText;

                    domains.AddDomain(domain);
                    logger.Debug(" domain added: " + domain + ".");

                    // Get aliases.
                    foreach (XmlNode aliasNode in domainNode.SelectNodes("alias"))
                    {
                        string alias = aliasNode.InnerText;

                        domains.AddDomainAlias(alias, domain);
                        logger.Debug("  alias added: " + alias + ".");
                    }
                }

                return domains;
            }
            else
            {
                logger.Warn("Cannot load Domains, no XML.");
                return null;
            }
        }

        public override List<UserRegistration> Load3rdPartyRegistrations()
        {
            List<UserRegistration> registrations = new List<UserRegistration>();

            foreach (SIPProvider sipProvider in m_sipProviders)
            {
                if (sipProvider.RegisterEnabled && sipProvider.RegisterAdminEnabled)
                {
                    registrations.Add(new UserRegistration(sipProvider));
                }
            }

            return registrations;
        }

        public override SIPRegistrarRecord LoadPersistedRegistrarRecord(SIPParameterlessURI addressOfRecord)
        {
            SIPAccount sipAccount = LoadSIPAccount(addressOfRecord.User, addressOfRecord.Host);

            if (sipAccount != null)
            {
                SIPRegistrarRecord registrarRecord = new SIPRegistrarRecord(null, sipAccount.SIPAccountId, addressOfRecord, addressOfRecord.User, sipAccount.SIPPassword, true);
                return registrarRecord;
            }

            return null;
        }
    }
}
